<template>
  <div
    v-loading="loading"
    element-loading-background="rgba(122, 122, 122, 0.5)"
    class="list-page"
  >
    <FormSearch
      ref="formSearch"
      :list="assertList"
      :data="assertData"
      v-bind="formSearchProps"
      @search="onSearch"
      @height-change="onFormSearchHeightChange"
      @visible-change="onVisibleChange"
    >
      <template v-for="config of list" :key="config.key" #[config.key]="scope">
        <slot :name="`form-${config.key}`" v-bind="scope"></slot>
      </template>
    </FormSearch>

    <WatchSize
      v-if="showOperation"
      class="list-page-operation"
      @height-change="onOperateHeightChange"
    >
      <slot name="operation"></slot>
    </WatchSize>

    <TableMain
      ref="tableMain"
      :api="api"
      :columns="columns"
      :query="queryMerge"
      :maxHeight="tableMaxHeight"
      :rowKey="assertRowKey"
      v-bind="tableMainProps"
      :refine="false"
      :injectInfo="injectInfoAdjust"
      @loading-change="onLoadingChange"
    >
      <template
        v-for="column of columns"
        :key="column.columnKey || column.prop"
        #[column.prop!]="scope"
      >
        <slot
          :name="`table-${column.prop}`"
          v-bind="assertColumnDefaultScope(scope)"
        >
        </slot>
      </template>

      <template
        v-for="column of columns"
        :key="column.columnKey || column.prop"
        #[`header-${column.prop}`]="scope"
      >
        <slot
          :name="`table-header-${column.columnKey || column.prop}`"
          v-bind="assertColumnHeaderScope(scope)"
          :searchFormData="assertData"
          :triggerSearch="formSearch?.triggerSearch"
        ></slot>
      </template>
    </TableMain>
  </div>
</template>
<script
  lang="ts"
  setup
  generic="
    T extends Record<string, any>,
    PO extends Record<string, any>,
    SO extends Record<string, any>,
    SQ extends Record<string, any>,
    F extends Record<string, any>
  "
>
import type {
  ExtractFormStringifyFromObject,
  FormItemConfigList,
  FormSearchInstance,
  FormSearchSearchType,
} from "@/components/form";
import {
  generateFormData,
  FormSearch,
  stringifyFormData,
} from "@/components/form";
import type {
  TableColumnDefaultScope,
  TableColumnHeaderScope,
  TableMainInstance,
} from "@/components/table";
import { BODY_CONTENT_VIEWPORT_HEIGHT } from "@/utils";
import WatchSize from "@/components/display/WatchSize.vue";
import { TableMain } from "@/components/table";
import type {
  ListPageInnerInjectInfo,
  ListPageInstance,
  ListPageProps,
} from "./types";
import type { UnwrapRef } from "vue";
import _pick from "lodash/pick";
import _cloneDeep from "lodash/cloneDeep";

type TableMainQuery = ExtractFormStringifyFromObject<PO, SO> & SQ;

const props = withDefaults(defineProps<ListPageProps<T, PO, SO, SQ, F>>(), {
  list: () => [],
  staticQuery: () => ({}),
  /** 展示操作区 */
  showOperation: false,
  refine: true,
  refineReduceHeight: 0,
});

/** 表单参数 */
const data = ref<PO>({} as unknown as PO);

/** stringify的表单及传递给表单的静态参数组合 */
const query = ref<ExtractFormStringifyFromObject<PO, SO>>(
  {} as unknown as ExtractFormStringifyFromObject<PO, SO>,
);

const loading = ref(false);

const formSearch = shallowRef<FormSearchInstance>();

const tableMain = shallowRef<TableMainInstance>();

const bodyContentViewportHeight: Ref<number> = inject(
  BODY_CONTENT_VIEWPORT_HEIGHT,
  500,
) as unknown as Ref<number>;

/** 搜索区高度 */
const searchHeight = ref(0);

/** 操作区高度 */
const operationHeight = ref(0);

const queryMerge = computed(() => {
  return {
    ...(query.value as ExtractFormStringifyFromObject<PO, SO>),
    ...props.staticQuery,
  } as unknown as TableMainQuery;
});

// const showSearch = computed(() => {
//   return props.list.length > 0;
// });
const showSearch = ref(false);

const searchHeightAdjust = computed(() => {
  if (!showSearch.value) {
    return 0;
  } else {
    return searchHeight.value + 10;
  }
});

const operationHeightAdjust = computed(() => {
  if (!props.showOperation) {
    return 0;
  } else {
    return operationHeight.value + 10;
  }
});

const listPageMinHeight = computed(() => {
  return bodyContentViewportHeight.value - props.refineReduceHeight;
});

const minHeight = computed(() => {
  return props.refine ? `${listPageMinHeight.value}px` : "auto";
});

const tableMaxHeight = computed(() => {
  return props.refine
    ? Math.max(
        listPageMinHeight.value -
          searchHeightAdjust.value -
          operationHeightAdjust.value,
        200,
      )
    : undefined;
});

const onSearch = (
  dataInit: Record<string, any>,
  type: FormSearchSearchType = "search",
) => {
  let data = dataInit;
  if (props.beforeSearch) {
    data = props.beforeSearch(
      _cloneDeep(dataInit) as ExtractFormStringifyFromObject<PO, SO>,
      type,
    );
  }

  // 重置时清空表格筛选
  if (type === "reset" && props.clearTableFilterOnReset) {
    tableMain.value?.getTableInstance()?.clearFilter();
  }

  if (JSON.stringify(data) === JSON.stringify(query.value)) {
    if (["reset", "search"].includes(type)) {
      // 参数未变化 视为重新加载
      // console.log("参数未变化 视为重新加载", type);
      return tableMain.value?.reload();
    } else {
      // console.log("参数未变化 不重新加载", type);
      return;
    }
  }
  query.value = {
    ...data,
  } as unknown as UnwrapRef<ExtractFormStringifyFromObject<PO, SO>>;
};

const onLoadingChange = (value: boolean) => {
  loading.value = value;
};

const onFormSearchHeightChange = (height: number) => {
  searchHeight.value = height;
};

const onOperateHeightChange = (height: number) => {
  operationHeight.value = height;
};

const assertList = computed(() => {
  return props.list as unknown as FormItemConfigList<
    Record<string, any>,
    Record<string, any>
  >;
});

const assertRowKey = computed(() => {
  return props.rowKey as Extract<keyof T, string> | ((row: T) => string);
});

/** TODO: vue 模版是基于html解析 使用as断言会有问题 此处先使用纯函数断言 后面通过terser配置处理这种内联函数 */
const assertData = computed(() => {
  return data.value as PO;
});

const expose: ListPageInstance<PO> = {
  refresh: (silent = false) => {
    return tableMain.value?.refresh(silent);
  },
  reload: (silent = false) => {
    return tableMain.value?.reload(silent);
  },
  getTableInstance: () => {
    return tableMain.value?.getTableInstance();
  },
  validate: () => {
    if (formSearch.value) {
      return formSearch.value.validate();
    } else {
      throw new Error("实例化未完成");
    }
  },
  resetFields: () => {
    return formSearch.value?.resetFields();
  },
  clearValidate: (key?: string) => {
    return formSearch.value?.clearValidate(key);
  },
  triggerReset: () => {
    return formSearch.value?.triggerReset();
  },
  triggerSearch: () => {
    return formSearch.value?.triggerSearch();
  },
  update: (key: Extract<keyof PO, string>, value: any) => {
    (data.value as PO)[key] = value;
  },
};

const injectInfoAdjust = computed(() => {
  const injectExtra = _pick(expose, [
    "triggerReset",
    "triggerSearch",
    "update",
  ]);
  return {
    ...(props.injectInfo || {}),
    ...injectExtra,
  };
});

/** TODO: vue 模版是基于html解析 使用as断言会有问题 此处先使用纯函数断言 后面通过terser配置处理这种内联函数 */
const assertColumnDefaultScope = (
  scope: TableColumnDefaultScope<T, ListPageInnerInjectInfo<PO, F>>,
) => {
  return scope;
};

/** TODO: vue 模版是基于html解析 使用as断言会有问题 此处先使用纯函数断言 后面通过terser配置处理这种内联函数 */
const assertColumnHeaderScope = (
  scope: TableColumnHeaderScope<T, ListPageInnerInjectInfo<PO, F>>,
) => {
  return scope;
};

const onVisibleChange = (status: boolean) => {
  showSearch.value = status;
};

defineExpose(expose);

watch(
  () => props.list,
  (n) => {
    const fData = generateFormData(n);
    data.value = fData as UnwrapRef<PO>;

    onSearch(stringifyFormData(fData, n));
  },
  {
    immediate: true,
  },
);
</script>
<style lang="less" scoped>
.list-page {
  &-operation {
    margin-bottom: 10px;
  }
  min-height: v-bind(minHeight);
  :deep(.el-loading-mask) {
    left: -10px;
    top: -10px;
    right: -10px;
    bottom: -10px;
  }
}
</style>
